﻿#include "../../includes/scripts/cjsmanager2.h"
#include "../../includes/QsLog/QsLog.h"
#include "../../includes/common/common.h"
#include "../../includes/common/fastlz.h"
#include "../../includes/common/NedAllocatorImpl.h"

#include <QDir>
#include <QFileInfo>
#include <QTextCodec>
#include <QLibrary>

typedef tagpluginItem* (*reg_fun)(CJsManager2 *g_JsManager);

initialiseSingleton(CJsManager2);

CJsManager2::CJsManager2(QObject *parent)
    : QObject(parent),
      m_isautoloadscript(false),
      m_isStartDebugServer(false),
      m_isEnableDebugMode(false),
      m_debugServerPort(0)
{
    connect(&m_SystemWorkingPathTimer, SIGNAL(timeout()),
            this, SLOT(WorkingdirectoryChanged()));
    connect(&m_pSystemWorkingPathWatcher, SIGNAL(fileChanged(QString)),
            this, SLOT(WorkingdirectoryChange(QString)));
    connect(&m_pSystemWorkingPathWatcher, SIGNAL(directoryChanged(QString)),
            this, SLOT(WorkingdirectoryChange(QString)));

    m_SystemWorkingPathTimer.setSingleShot(true);    
    m_ScriptEngineDebugger.attachTo(&m_ScriptEngine);
    m_ScriptEngineDebugger.setAutoShowStandardWindow(false);
//    m_ScriptEngineDebugger.setScriptBreakpoint();
}

CJsManager2::~CJsManager2()
{
    this->clean_system();
}

void CJsManager2::WorkingdirectoryChange(const QString &path)
{
    m_SystemWorkingPathTimer.start(2000);
}

void CJsManager2::WorkingdirectoryChanged(void)
{

}

/**
 * @brief CJsManager2::init_system 初始化系统
 *
 * @param workingpath 工作目录
 * @param regdllpath 插件目录
 * @param debugserverport 调试服务器端口
 * @param isauotoloadscript 脚本更改后是否重新导入
 * @param isStartDebugServer 是否开启调试服务器
 * @param isEnableDebugMode 是否启用调试模式
 *
 * @return 如果脚本系统初始成功返回真，否则返回假
 */
bool CJsManager2::init_system(QString workingpath,
                 QString regdllpath,
                 int debugserverport,
                 bool isauotoloadscript,
                 bool isStartDebugServer,
                 bool isEnableDebugMode)
{
    if(workingpath.isEmpty())
        return false;

    this->clean_system();

    // 设置工作目录
    this->attachCurrentWorkingPath(workingpath,true);

    // 设置插件目录
    this->setRegDllPath(regdllpath);

    // 设置是否自动导入脚本
    this->setAutoloadScript(isauotoloadscript);

    // 设置是否使用调试
    this->setEnableDebugMode(isEnableDebugMode);

    // 是否启用网络
    if(isStartDebugServer && debugserverport > 0)
    {
        startDebugServer(debugserverport);
    }

    RegisterNewObject("sys",this,true);

    // 导入注册的插件
    reg_all2jss();

    // 导入脚本文件
    if(isauotoloadscript && js_loadscriptstring_callback(m_currentWorkingPath))
        js_setup_callback();

    QString tmpMsg2 = QString::fromLocal8Bit(" <b>启动调试服务器，端口:</b>")+
            QString::asprintf("%d",debugserverport);

    QString logmsg =
            QString::fromLocal8Bit("脚本系统初始成功:")+
            QString::fromLocal8Bit(" <b>工作目录:</b>")+workingpath+
            QString::fromLocal8Bit(" <b>插件目录:</b>")+regdllpath+
            (isStartDebugServer ? tmpMsg2 : "")+
            QString::fromLocal8Bit(" <b>是否启用调试:</b>")+QString::asprintf("%d",(int)isEnableDebugMode)+
            QString::fromLocal8Bit(" <b>自动加载脚本:</b>")+QString::asprintf("%d",isauotoloadscript);
    Log(logmsg,"info",false);

    return true;
}

/**
 * @brief CJsManager2::clean_system 卸载系统
 */
void CJsManager2::clean_system(void)
{
    this->cleanallplugin();

    QStringList tmpStringList = m_pSystemWorkingPathWatcher.files() +
                                m_pSystemWorkingPathWatcher.directories();
    m_pSystemWorkingPathWatcher.removePaths(tmpStringList);
    m_allResourcesPath.clear();

    if(m_WebSocketServer.isListening())
        m_WebSocketServer.CloseServer();
}

/**
 * @brief CJsManager2::cleanallplugin 清除所有的插件
 */
void CJsManager2::cleanallplugin(void)
{
    QHash<QString,tagpluginItem*>::iterator iter = m_LibraryList.begin();
    for(;iter != m_LibraryList.end();++iter)
    {
        iter.value()->clear();
        delete iter.value();
    }

    m_LibraryList.clear();
}


/**
 * @brief CJsManager2::reg_all2jss 注册所有用到的函数
 */
void CJsManager2::reg_all2jss(void)
{
    if(m_regDllPath.isEmpty())
        return;

    QVector<QString> pfilelist;
    if(FindFile(m_regDllPath,pfilelist) < 0)
        return;

    for(int i=0;i<pfilelist.size();i++)
    {
        QFileInfo pFileInfo(pfilelist[i]);
        if(!pFileInfo.exists())
            continue;

#if defined (WIN32)
        if(pFileInfo.suffix().toLower() != "dll")
            continue;
#else
        if(pFileInfo.suffix().toLower() != "so")
            continue;
#endif

        QLibrary *pDllLibrary = new QLibrary(pfilelist[i]);
        if(!pDllLibrary->load())
        {
            QLOG_ERROR() << "load dll:"<<pfilelist[i]<<" fail.";
            Log(QString::fromLocal8Bit("导入插件:")+
                pfilelist[i]+
                QString::fromLocal8Bit("失败."),"error",false);

            continue;
        }

        reg_fun reg_xxx2Jss = (reg_fun)pDllLibrary->resolve("reg_xxx2Jss");
        if(reg_xxx2Jss)
        {
            tagpluginItem *ppluginItem = reg_xxx2Jss(this);
            if(ppluginItem)
            {
                ppluginItem->lib = pDllLibrary;
                m_LibraryList[ppluginItem->name] = ppluginItem;
                Log(QString::fromLocal8Bit("导入插件:")+
                    ppluginItem->name+
                    QString::fromLocal8Bit("成功."),"info",false);
            }
        }

        //pDllLibrary.unload();
    }
}

/**
 * @brief CJsManager2::js_setup_callback 初始脚本系统
 */
void CJsManager2::js_setup_callback(void)
{
    emit on_signalsystemsetup();

    QScriptValue run_fun = m_ScriptEngine.globalObject().property("setup");
    if(run_fun.isNull())
        return;

    QScriptValue errorValue = run_fun.call();
    if(errorValue.isError())
    {
        QString logmsg = QString::fromLocal8Bit("js_setup_callback 出错:");
        logmsg += errorValue.property("name").toString() + ", "
                 + errorValue.property("message").toString() + ", "
                 //+ errorValue.property("lineNumber").toInt() + ", "
                 + errorValue.property("stack").toString();
        Log(logmsg,"error",false);
    }
}

/**
 * @brief CJsManager2::js_loadscriptstring_callback 从指定文件中导入脚本并执行
 * @param scriptfilepath 要导入的脚本文件路径
 */
bool CJsManager2::js_loadscriptstring_callback(QString scriptfilepath,bool isCheckMd5)
{
    if(scriptfilepath.isEmpty())
        return false;

    QString tmpscriptfilepath = scriptfilepath;

    if(tmpscriptfilepath.isEmpty())
        tmpscriptfilepath = m_currentWorkingPath;

    QVector<QString> pfilelist;
    if(FindFile(tmpscriptfilepath,pfilelist) < 0)
        return false;

    QString contents;

    for(int i=0;i<pfilelist.size();i++)
    {
        QFileInfo pFileInfo(pfilelist[i]);
        if(!pFileInfo.exists() || pFileInfo.suffix().toLower() != "js")
            continue;

        QFile scriptFile(pfilelist[i]);
        if (!scriptFile.open(QIODevice::ReadOnly))
        {
            QString logmsg = scriptfilepath + QString::fromLocal8Bit(" 导入失败.");
            Log(logmsg,"error",false);
            continue;
        }

        QTextStream stream(&scriptFile);
        contents += stream.readAll();
        scriptFile.close();
    }

    QString pcurrentScriptMd5 = getByteArrayMd5(QByteArray(contents.toLatin1()));

    // 如果文件没有什么改变，就不加载了
    if(isCheckMd5 &&
            m_currentScriptMd5 == pcurrentScriptMd5)
    {
        Log(QString::fromLocal8Bit("js_loadscriptstring_callback 执行失败:文件相同.(isCheckMd5:")+
            QString::asprintf("%d)",(int)isCheckMd5),
            "info",
            false);
        return false;
    }

    m_currentScriptMd5 = pcurrentScriptMd5;

    m_ScriptEngine.evaluate(contents,scriptfilepath);

    m_ScriptEngine.evaluate(contents, scriptfilepath);
    if (m_ScriptEngine.hasUncaughtException())
    {
        QStringList bt = m_ScriptEngine.uncaughtExceptionBacktrace();
        QString err = m_ScriptEngine.uncaughtException().toString();

        QString logmsg = QString::fromLocal8Bit("js_loadscriptstring_callback 出错:")+
            qPrintable(err)+","+
            qPrintable(bt.join(","));
        Log(logmsg,"error",false);

        return false;
    }
    else
    {
        Log(QString::fromLocal8Bit("js_loadscriptstring_callback 执行成功."),"info",false);
    }

    return true;
}

/**
 * @brief CJsManager2::RegisterNewObject 注册一个新的对象
 * @param objName 新对象的名称
 * @param newobject 要注册的对象
 * @param isCppOwnership 如果设置成真表示归c++所有，如果设置成假表示归JS所有，JS在退出时会自动删除这个对象
 * @return 返回注册的值
 */
QScriptValue CJsManager2::RegisterNewObject(QString objName,QObject *newobject,bool isCppOwnership)
{
    QScriptValue returnObj = m_ScriptEngine.newQObject(newobject,
                                                       (isCppOwnership ? QScriptEngine::QtOwnership : QScriptEngine::ScriptOwnership));
    if (returnObj.isError())
    {
        QString logmsg = "RegisterNewObject error:";
        logmsg += returnObj.property("name").toString() + ", "
                 + returnObj.property("message").toString() + ", "
                 //+ errorValue.property("lineNumber").toInt() + ", "
                 + returnObj.property("stack").toString();
        Log(logmsg,"error",false);

        return returnObj;
    }

    m_ScriptEngine.globalObject().setProperty(objName, returnObj);

    return returnObj;
}

/**
 * @brief CJsManager2::SendLog 发送日志信息
 * @param msg 要发送的日志信息
 * @param type 日志类型
 * @param isuseGBK 是否使用GBK,从脚本过来的使用GBK
 * @param isPrint 是否打印
 * @param isSend 是否网络发送
 * @param isLog 是否本地记录
 * @param isProg 是否要显示代码信息，初始不显示
 */
void CJsManager2::Log(QString msg, QString type,
                     bool isuseGBK,
                     bool isPrint,bool isSend,
                     bool isLog,bool isProg)
{
    if(msg == "" || type == "")
        return;

    QString tempMsg = msg;

    if(isuseGBK)
    {
        QTextCodec* pGBKCodec = QTextCodec::codecForName("UTF-8");
        tempMsg = QString::fromUtf8(pGBKCodec->fromUnicode(msg.simplified()));
    }

    QString content = QString::asprintf("%s:%d",
                                        //type.toStdString().c_str(),
                                        //QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss").toStdString().c_str(),
                                        __FILE__,
                                        __LINE__)+tempMsg;

    if(!isProg)
    {
        content = tempMsg;
    }

    if(isPrint)
        qDebug()<<content;

    if(isLog)
    {
        if(type == "info")
            QLOG_INFO()<<content;
        else if(type == "warn")
            QLOG_WARN()<<content;
        else if(type == "debug")
            QLOG_DEBUG()<<content;
        else if(type == "error")
            QLOG_ERROR()<<content;
        else if(type == "fatal")
            QLOG_FATAL()<<content;
    }

    emit on_signalLog(content,type);

    if(isSend)
    {
        QJsonObject mesObjReturn;
        mesObjReturn["mesid"] = IDD_JS_MESSAGE_LOG;
        mesObjReturn["type"] = type;
        mesObjReturn["content"] = content;

        m_WebSocketServer.SendAll(JsonToString(mesObjReturn));
    }
}

/**
 * @brief CJsManager2::startDebugServer 启动调试服务器
 * @param serverport
 * @return
 */
bool CJsManager2::startDebugServer(int serverport)
{
    if(m_WebSocketServer.isListening())
        return false;

    m_debugServerPort = serverport;
    m_WebSocketServer.OpenServer(m_debugServerPort);
}

/**
 * @brief CJsManager2::closeDebugServer 关闭调试服务器
 */
void CJsManager2::closeDebugServer(void)
{
    m_WebSocketServer.CloseServer();
}

/**
 * @brief CJsManager2::isstardedDebugServer 检测是否启动调试服务器
 * @return
 */
bool CJsManager2::isstardedDebugServer(void)
{
    return m_WebSocketServer.isListening();
}

/**
 * @brief CJsManager2::runScript 开始执行脚本
 */
void CJsManager2::runScript(void)
{
    if(js_loadscriptstring_callback(m_currentWorkingPath))
        js_setup_callback();
}

/**
 * @brief CJsManager2::attachCurrentWorkingPath 设置当前工作目录
 * @param path 要附加的目录
 * @param isCurrentpath 当前附加的目录是否为当前目录
 */
void CJsManager2::attachCurrentWorkingPath(QString path,bool isCurrentpath)
{
    if(path.isEmpty())
        return;

    //判断路径是否存在
    QDir dir(path);
    if(!dir.exists())
    {
        Log(path+QString::fromLocal8Bit("不存在."),"error",false);
        return;
    }

    if(isCurrentpath) m_currentWorkingPath = path;
    m_pSystemWorkingPathWatcher.addPath(path);
    m_allResourcesPath.push_back(path);

    //监控指定目录下所有的文件
    Monitoringdirectory();
}

/**
 * @brief CJsManager2::Monitoringdirectory 监控指定目录下所有的文件
 * @param directory
 */
void CJsManager2::Monitoringdirectory(void)
{
    for(int i=0;i<m_allResourcesPath.size();i++)
    {
        QFileInfo pFileInfo(m_allResourcesPath[i]);
        if(!pFileInfo.exists())
            return;

        QVector<QString> pfilelist;
        if(FindFile(m_allResourcesPath[i],pfilelist) < 0)
            return;

        for(int i=0;i<pfilelist.size();i++)
            m_pSystemWorkingPathWatcher.addPath(pfilelist[i]);
    }
}

/**
 * @brief CJsManager2::OnProcessNetText 处理网络字符串消息
 * @param conn
 * @param mes
 */
void CJsManager2::OnProcessNetText(QWebSocket *conn,QString mes)
{

}

/**
 * @brief CJsManager2::OnProcessNetBinary 处理网络二进制消息
 * @param conn
 * @param data
 */
void CJsManager2::OnProcessNetBinary(QWebSocket *conn,QByteArray &data)
{

}

/**
 * @brief CJsManager2::OnProcessConnectedNetMes 处理一个新的连接到达
 * @param conn
 */
void CJsManager2::OnProcessConnectedNetMes(QWebSocket *conn)
{

}

/**
 * @brief CJsManager2::OnProcessDisconnectedNetMes 处理一个连接关闭
 * @param conn
 */
void CJsManager2::OnProcessDisconnectedNetMes(QWebSocket *conn)
{

}

/**
 * @brief CJsManager2::OnProcessBinaryOperProcess 处理二进制文件发送，接收进度，type:0发送，1接收
 * @param conn
 * @param type
 * @param sendsize
 * @param totalsize
 */
void CJsManager2::OnProcessBinaryOperProcess(QWebSocket *conn,int type,qint64 sendsize,qint64 totalsize)
{

}
